/*  
	iImager (network disk imaging via iscsi)
	Copyright (C) 2009-2010 Yao Wei <njustyw@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "Q_ImagerTask.h"
#include "Q_RWThread.h"
#include "Q_LogDB.h"
#include "Q_HDObject.h"
#include "Q_HDDuplicator.h"

ImagerTask::ImagerTask(QObject* parent /* = 0 */):QObject(parent)
{
	DEBUG_MSG("Enter");
	
	for(int i = 0; i < MAX_COPYING_DISKS_THE_SAME_TIME; i++)
	{
		itemMap[i] = -1;
		copyingHDs[i] = 0;
		mt_buffer[i] = allocate_qbuffer_for_thread_copy();
	}

	runningCount = 0;
}

ImagerTask::~ImagerTask()
{
	DEBUG_MSG("Leave");
	
	for(QHash<HDObject*, RunningDisk>::iterator it = runningHDs.begin(); it != runningHDs.end(); it++)
	{
		if(it->runningState.state == HD_Imaging || it->runningState.state == HD_Paused)
		{
			HDObject* obj = it.key();

			it->threadParam->cond_mutex.lock();
			it->threadParam->ispause.fetchAndStoreOrdered(0);
			it->threadParam->cond_mutex.unlock();			
			releaseForMT(obj, it->threadParam);			
		}
	}

	for(int i = 0; i < MAX_COPYING_DISKS_THE_SAME_TIME; i++)
	{
		release_qbuffer_for_thread_copy(mt_buffer[i]);
	}
}

int ImagerTask::findItemFromMap(const HDObject* object)
{
	for(int i = 0; i < MAX_COPYING_DISKS_THE_SAME_TIME; i++)
		if(copyingHDs[i] == object)	
			return i;

	return -1;
}

int ImagerTask::findItemNotUsedFromMap()
{
	for(int i = 0; i < MAX_COPYING_DISKS_THE_SAME_TIME; i++)
		if(itemMap[i] == -1)	
			return i;

	return -1;
}

void ImagerTask::makeOneRID(const QString& fileName, int imagingType, int hashType, HDObject* obj)
{
	struct RunningDisk& one = runningHDs[obj];
	
	struct ImagingTaskStatus st;
	memset(&st, 0, sizeof(st));
	st.state = HD_ReadyImaging;
	strncpy_s(st.ip, obj->ip.toLatin1().data(), sizeof(st.ip));
	st.location = obj->locationIndex;

	one.runningState = st;
	one.filename = fileName;
	one.imagingType = imagingType;
	one.hashType = hashType;
	one.threadParam = 0;
}

bool ImagerTask::readyForImaging(HDObject* obj, const QString& fileName, quint8 imagingType, 
								 quint8 hashType, const QString& caseNo)
{
	if(runningHDs.contains(obj))
		return false;

	LogDB::instance()->addDiskReadyForCopyLog(obj->hdId, obj->hdlocId, fileName, imagingType, hashType, caseNo, obj->rId);
	obj->setState(HD_ReadyImaging);
	makeOneRID(fileName, imagingType, hashType, obj);

	return true;
}

bool ImagerTask::startImaging(HDObject* obj)
{
	QReadThread* reader = 0;
	QVerifyThread* verifier1 = 0;
	QWriteThread* writer = 0;
	Q_MT_LOCKER* locker = 0;
	struct Q_MT_PARAM* param = 0;
	int mapIndex;
	QMutexLocker ml(&mutex);

	if(!runningHDs.contains(obj))
		goto clean0;

	mapIndex = findItemFromMap(obj);
	if(mapIndex == -1)
	{
		mapIndex = findItemNotUsedFromMap();/* because only max_coping_item are allowed coping at the one time*/
		if(mapIndex == -1)	
			goto clean0;

		itemMap[mapIndex] = mapIndex;
		copyingHDs[mapIndex] = obj;
		runningCount ++;
	}
	ml.unlock();
	
	param = allocate_param_for_thread_copy();
	if(!param)
		goto clean0;
	if(!initialize_device_and_imaging_handle(param, obj->devname, runningHDs[obj].filename, runningHDs[obj].imagingType, runningHDs[obj].hashType))
		goto clean0;

	//! step 4
	obj->setState(HD_Imaging);
	runningHDs[obj].runningState.state = HD_Imaging;

	reader = new QReadThread(0);
	verifier1 = new QVerifyThread(0);
	writer = new QWriteThread(0);
	locker = allocate_qlocker_for_thread_copy();

	initialize_qbuffer_state(mt_buffer[mapIndex], runningHDs[obj].hashType);
	param->isdrop.fetchAndStoreOrdered(0);
	param->ispause.fetchAndStoreOrdered(0);
	param->buffer = mt_buffer[mapIndex];
	param->locker = locker;
	param->obj = obj;
	param->readThread = reader;
	param->verifyThread = verifier1;
	param->writeThread = writer;
	runningHDs[obj].threadParam = param;

	connect(writer, SIGNAL(signal_finished(HDObject*, double, int, struct Q_MT_PARAM*)), this, SLOT(doneImagingTask_MT(HDObject*, double, int, struct Q_MT_PARAM*)));
	connect(reader, SIGNAL(signal_exceptioned(HDObject*, char*, struct Q_MT_PARAM*)), this, SLOT(showException_MT(HDObject*, char*, struct Q_MT_PARAM*)));
	connect(writer, SIGNAL(signal_exceptioned(HDObject*, char*, struct Q_MT_PARAM*)), this, SLOT(showException_MT(HDObject*, char*, struct Q_MT_PARAM*)));
	connect(reader, SIGNAL(signal_cancel(HDObject*, struct Q_MT_PARAM*)), this, SLOT(dropImagingTask_MT(HDObject*, struct Q_MT_PARAM*)));
	connect(writer, SIGNAL(signal_cancel(HDObject*, struct Q_MT_PARAM*)), this, SLOT(dropImagingTask_MT(HDObject*, struct Q_MT_PARAM*)));
	connect(reader, SIGNAL(signal_updateStatus(HDObject*, int, double, int, int)), this, SLOT(updateProgress_MT(HDObject*, int, double, int, int)));
	connect(writer, SIGNAL(signal_updateStatus(HDObject*, int, double, int, int)), this, SLOT(updateProgress_MT(HDObject*, int, double, int, int)));

	reader->setParam(param);
	verifier1->setParam(param, runningHDs[obj].hashType);
	writer->setParam(param);

	LogDB::instance()->addDiskStartCopyLog(obj->hdId, obj->hdlocId, obj->rId);
	LogDB::instance()->startRunning(obj->rId);

	reader->start();
	verifier1->start();
	writer->start();
	
	return true;

clean0:
	obj->setState(HD_Normal);
	runningHDs[obj].runningState.state = HD_Normal;
	cleanup_device_and_imaging_handle(param);
	cleanUpImaging_Map(obj);

	return false;
}

bool ImagerTask::pauseImaging(HDObject* obj)
{
	if(!runningHDs.contains(obj))
		return false;

	runningHDs[obj].runningState.speed = 0;
	runningHDs[obj].runningState.state = HD_Paused;
	
	runningHDs[obj].threadParam->ispause.fetchAndStoreOrdered(1);

	emit imagingPausedDone();
	return true;
}

bool ImagerTask::continueImaging(HDObject* obj)
{
	if(!runningHDs.contains(obj))
		return false;

	runningHDs[obj].runningState.speed = 0;
	runningHDs[obj].runningState.state = HD_Imaging;

	runningHDs[obj].threadParam->isdrop.fetchAndStoreOrdered(0);
	runningHDs[obj].threadParam->ispause.fetchAndStoreOrdered(0);
	runningHDs[obj].threadParam->pause_cond.wakeAll();

	emit ImagingContinuedDone();

	return true;
}

bool ImagerTask::cancelImaging(HDObject* obj)
{
	if(!runningHDs.contains(obj))
		return false;

	memset(&runningHDs[obj].runningState, 0, sizeof(ImagingTaskStatus));
	runningHDs[obj].runningState.state = HD_DropImaging;

	runningHDs[obj].threadParam->isdrop.fetchAndStoreOrdered(1);
	runningHDs[obj].threadParam->ispause.fetchAndStoreOrdered(0);
	runningHDs[obj].threadParam->pause_cond.wakeAll();
	return true;
}

void ImagerTask::updateProgress_MT(HDObject* obj, int percentage, double currentSpeed, 
										int remainingTime, int elapsedTime)
{
	//DEBUG3_MSG(QString("(%1, %2): %3%, %4 GiB/min, est. %5 sec").arg(obj->rackIndex+1).arg(obj->locationIndex+1).
	//			arg(percentage).arg(currentSpeed).arg(remainingTime));

	if(!runningHDs.contains(obj))
		return;

	ImagingTaskStatus& running = runningHDs[obj].runningState;
	running.speed = currentSpeed;
	running.percentage = percentage;
	running.elapsedtime = elapsedTime;
	running.remaingtime = remainingTime;
}

void ImagerTask::doneImagingTask_MT(HDObject* obj, double meanSpeed, int totalTime, struct Q_MT_PARAM* param)
{
	DEBUG3_MSG(__FUNCTION__);
	QString Msg1, Msg2;

	if(totalTime < 60)
		Msg1 = tr("Time cost %1 sec, average speed %2 GiB/min").arg(totalTime).arg(meanSpeed, 0, 'g', 3);
	else if(totalTime < 3600)
		Msg1 = tr("Time cost %1 min %2 sec, average speed %3 GiB/min").arg(totalTime/60).arg(totalTime%60).arg(meanSpeed, 0, 'g', 3);
	else
		Msg1 = tr("Time cost %1 hour %2 min %3 sec, average speed %4 GiB/min").arg(totalTime/3600).arg(totalTime%3600/60).arg(totalTime%3600%60).arg(meanSpeed, 0, 'g', 3);

	Msg2 = tr("the %1-th rack, the %2-th disk imaging task is completed, %3").arg(obj->rackIndex+1).arg(obj->locationIndex+1).arg(Msg1);

	LogDB::instance()->doneRunning(obj->rId);
	LogDB::instance()->addDiskDoneCopyLog(obj->hdId, obj->hdlocId, obj->rId, meanSpeed, totalTime);

	if(!runningHDs.contains(obj))	
		return;

	runningHDs[obj].runningState.state = HD_DoneImaging;
	obj->setState(HD_DoneImaging);

	obj->setState(HD_Normal);

	cleanUpImaging_Map(obj);

	releaseForMT(obj, param);

	QTimer::singleShot(500, this, SLOT(wakeAnotherImagingTask()));
}

void ImagerTask::showException_MT(HDObject* obj, char* msg, struct Q_MT_PARAM* param)
{
    QString errorMsg = tr("The %1-th rack, the %2-th disk imaging occured something error, since %3").
		arg(obj->rackIndex+1).arg(obj->locationIndex+1).arg(QString::fromLocal8Bit(msg));
	LogDB::instance()->addExceptionCopyLog(obj->hdId, obj->hdlocId, QString::fromLocal8Bit(msg), obj->rId);

	if(!runningHDs.contains(obj))
		return;

	QMutexLocker locker(&mutex);
	
	runningHDs[obj].runningState.state = HD_ErrorImaging;
	obj->setState(HD_ErrorImaging);

	locker.unlock();

	cleanup_device_and_imaging_handle(runningHDs[obj].threadParam);
	obj->setState(HD_Normal);

	cleanUpImaging_Map(obj);

	releaseForMT(obj, param);

	QTimer::singleShot(500, this, SLOT(wakeAnotherImagingTask()));
}

void ImagerTask::dropImagingTask_MT(HDObject* obj, struct Q_MT_PARAM* param)
{
	LogDB::instance()->addDiskDropCopyLog(obj->hdId, obj->hdlocId, obj->rId);
	LogDB::instance()->dropRunning(obj->rId);

	if(!runningHDs.contains(obj))
		return;
	
	cleanup_device_and_imaging_handle(runningHDs[obj].threadParam);
	obj->setState(HD_Normal);

	cleanUpImaging_Map(obj);

	releaseForMT(obj, param);

	QTimer::singleShot(500, this, SLOT(wakeAnotherImagingTask()));
}

void ImagerTask::releaseForMT(HDObject* obj, struct Q_MT_PARAM* param)
{
    Q_UNUSED(obj);

	QReadThread* rt = param->readThread;
	QWriteThread* wt = param->writeThread;
	QVerifyThread* vt1 = param->verifyThread;
	if(rt->isRunning())	rt->terminate();
	if(wt->isRunning())	wt->terminate(); 
	if(vt1->isRunning()) vt1->terminate();
    rt->deleteLater();
    wt->deleteLater();
    vt1->deleteLater();
	release_qlocker_for_thread_copy(param->locker);
	release_param_for_thread_copy(param);
}

void ImagerTask::cleanUpImaging_Map(HDObject* obj)
{
    DEBUG3_MSG(__FUNCTION__);
 
	if(runningHDs.contains(obj))	
	{
        if(runningHDs[obj].runningState.state == HD_DropImaging
                || runningHDs[obj].runningState.state == HD_DoneImaging
                || runningHDs[obj].runningState.state == HD_ErrorImaging)

		{
			runningHDs.remove(obj);
		}
	}

	QMutexLocker ml2(&mutex);
	int mapIndex;
	if((mapIndex = findItemFromMap(obj)) < 0)	
		return;
	if(mapIndex >= 0 && mapIndex < MAX_COPYING_DISKS_THE_SAME_TIME)
	{
		copyingHDs[mapIndex] = 0;
		itemMap[mapIndex] = -1;
		runningCount --;	
	}
}

void ImagerTask::wakeAnotherImagingTask()
{
    DEBUG3_MSG(__FUNCTION__);
    QMutexLocker ml2(&mutex);
	
	for(QHash<HDObject*, RunningDisk>::const_iterator it = runningHDs.begin(); it != runningHDs.end(); it++)
	{
		if(it->runningState.state == HD_ReadyImaging)
		{
			ml2.unlock();
			startImaging(it.key());
			break;
		}
		else if(it->runningState.state == HD_Paused)
		{
			ml2.unlock();
			//continueImaging_UI(i);
			break;
		}
	}
}

int ImagerTask::countOfRunningTask()const
{
	int count = 0;
	for(QHash<HDObject*, RunningDisk>::const_iterator it = runningHDs.begin(); it != runningHDs.end(); it++)
		if(it->runningState.state == HD_Imaging)
			count ++;

	return count;
}
